//
// Created by 余代宸 -Yu DaiChen on 2021/12/18
// QQ-E-MAIL:250074249@qq.com
//

#include "cMalloc.h"

//已经使用的内存，malloc函数增加，free减少这个值
static size_t usedMemory = 0;
// 线程安全
static int mallocThreadSafe = 0;

#ifdef __unix__
#define updateMemoryAdd(__n) do { \
    pthread_mutex_lock(&usedMemoryMutex); \
    usedMemory += (__n); \
    pthread_mutex_unlock(&usedMemoryMutex); \
} while(0)

/**
 * @brief 减小全局变量记录  used_memory（记录的消耗的内存）
 */
#define updateMemorySub(__n) do { \
    pthread_mutex_lock(&usedMemoryMutex); \
    usedMemory -= (__n); \
    pthread_mutex_unlock(&usedMemoryMutex); \
} while(0)

/**
 * @brief 有两种方法创建互斥锁，静态方式和动态方式。
 * POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁，方法如下：
 * pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
 * 在LinuxThreads实现中，pthread_mutex_t是一个结构，而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
 *		PTHREAD_MUTEX_NORMAL ： 不提供死锁检测
 * 		PTHREAD_MUTEX_ERRORCHECK ：提供错误检查，尝试重新锁定的互斥锁已经由该线程锁定 或者 解除锁定的互斥锁不是由该线程锁定或者未锁定 返回错误
 * 		PTHREAD_MUTEX_RECURSIVE ：锁定计数，锁住 +1 ，解除 -1 ，0可以获取
 * 		PTHREAD_MUTEX_DEFAULT ： 以递归方式锁定，尝试解除对它的锁定、解除锁定尚未锁定的互斥锁，则会产生不确定的行为
 * POSIX这个名称是由理查德·斯托曼（RMS）应IEEE的要求而提议的一个易于记忆的名称。
 * 它基本上是Portable Operating System Interface（可移植操作系统接口）的缩写，而X则表明其对Unix API的传承
 */
pthread_mutex_t usedMemoryMutex = PTHREAD_MUTEX_INITIALIZER;
#else
#define updateMemoryAdd(__n) do { \
    usedMemory += (__n); \
} while(0)

#define updateMemorySub(__n) do { \
    usedMemory -= (__n); \
} while(0)

#endif // __unix__

/**
 * @brief  更新内存统计分配
 * if (_n&(sizeof(long)-1))  ==  (_n&7==0) == 判断内存空间的大小是不是8的倍数
 * malloc()本身能够保证所分配的内存是8字节对齐的：如果你要分配的内存不是8的倍数，那么malloc就会多分配一点，来凑成8的倍数。
 * 所以update_zmalloc_stat_alloc函数（或者说zmalloc()相对malloc()而言）
 * 真正要实现的功能并不是进行8字节对齐（malloc已经保证了），它的真正目的是使变量used_memory精确的维护实际已分配内存的大小
 * 如果64位系统种 内存大小不是8的倍数，就加上相应的偏移量使之变成8的倍数
 *
 * mallocThreadSafe 保证线程是否安全
 */
#define updateMemoryStatAlloc(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    if (mallocThreadSafe) { \
        updateMemoryAdd(_n); \
    } else { \
        usedMemory += _n; \
    } \
} while(0)

/**
 * @brief updateMemoryStatFree与updateMemoryStatAlloc相对应，但功能相反，提供线程安全地usedMemory减法操作。
 */
#define updateMemoryStatFree(__n) do { \
    size_t _n = (__n); \
    if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
    if (mallocThreadSafe) { \
        updateMemorySub(_n); \
    } else { \
        usedMemory -= _n; \
    } \
} while(0)


static void defaultOutOfMemoryHandler(size_t size) {
	fprintf(stderr, "Out of memory trying to allocate %zu bytes\n",
	        size);
	fflush(stderr);//强迫将缓冲区内的数据写回参数stream 指定的文件中
	abort();//引发不正常进程的终止
}

/**
 * @brief out of Memory溢出提示函数 一个函数指针指向函数 defaultOutOfMemoryHandler
 */
static void (*outOfMemoryHandler)(size_t) = defaultOutOfMemoryHandler;

// 设置OOM内存溢出后的方法
void setOutOfMemoryHandler(void (*oomHandler)(size_t)) {
	outOfMemoryHandler = oomHandler;
}

void *cMalloc(size_t size) {
	void *ptr = malloc(size + PREFIX_SIZE);
	if (!ptr) {
		outOfMemoryHandler(size);
	}
#ifdef HAVE_MALLOC_SIZE    // mallocSize()方法的情况下
	updateMemoryStatAlloc(cmallocSize(ptr));
	return ptr;
#else	// 系统没有 mallocSize()函数 需要手动记录PREFIX_SIZE(分配的内存块大小)
	*((size_t *) ptr) = size;
	updateMemoryStatAlloc(size + PREFIX_SIZE);
	return (char *) ptr + PREFIX_SIZE;
#endif
	return NULL;
}

void *cMallocWithZero(size_t size) {
	void *ptr = calloc(1, size + PREFIX_SIZE);
	if (!ptr) {
		outOfMemoryHandler(size);
	};
#ifdef HAVE_MALLOC_SIZE
	updateMemoryStatAlloc(cmallocSize(ptr));
	return ptr;
#else
	*((size_t *) ptr) = size;
	updateMemoryStatAlloc(size + PREFIX_SIZE);
	return (char *) ptr + PREFIX_SIZE;
#endif
}

void *cReMalloc(void *ptr, size_t size) {
#ifndef HAVE_MALLOC_SIZE
	void *realPtr;
#endif
	size_t oldSize;
	void *newPtr;
	if (ptr == NULL) { return cMalloc(size); }
#ifdef HAVE_MALLOC_SIZE
	oldsize = cmallocSize(ptr);
	newptr = realloc(ptr,size);
	if (!newptr) {
		outOfMemoryHandler(size);
	}
	updateMemoryStatFree(oldsize);
	updateMemoryStatAlloc(cmallocSize(newptr));
	return newptr;
#else
	// 1、减去头部8字节的信息
	realPtr = (char *) ptr - PREFIX_SIZE;
	// 2、获取原来的大小
	oldSize = *((size_t *) realPtr);
	// 3、调用 realloc() 函数重新分配空间
	newPtr = realloc(realPtr, size + PREFIX_SIZE);
	// 4、如果为空 out of memory
	if (!newPtr) {
		outOfMemoryHandler(size);
	}
	// 5、重新分配空间的newPtr空间
	*((size_t *) newPtr) = size;
	// 6、use_memory减去旧空间
	updateMemoryStatFree(oldSize);
	// 7、use_memory加上新的空间
	updateMemoryStatAlloc(size);
	// 返回新开辟的空间  newPtr + 向右偏移位8指针
	return (char *) newPtr + PREFIX_SIZE;
#endif
}


void cFree(void *ptr) {
#ifndef HAVE_MALLOC_SIZE
	void *realPtr;
	size_t oldSize;
#endif
	if (ptr == NULL) {
		return;
	};
#ifdef HAVE_MALLOC_SIZE
	updateMemoryStatFree(cmallocSize(ptr));
	free(ptr);
#else
	// 1 、指针ptr 64位下 向前偏移8个字节的长度
	realPtr = (char *) ptr - PREFIX_SIZE;
	// 2 、得到最初需要分配的内存大小
	oldSize = *((size_t *) realPtr);
	//3 、在线程安全的情况下，减去use_memory 总消耗的内存量
	updateMemoryStatFree(oldSize + PREFIX_SIZE);
	// 4、清除空间
	free(realPtr);
#endif
}

char *strDup(const char *str) {
	//先获得字符串s的长度，strlen()函数不统计结束'0'的，所以最后要加1
	size_t size = strlen(str) + 1;
	// cMalloc()来分配足够的空间，首地址为p
	char *newStr = cMalloc(size);
	// 调用memcpy来完成复制
	memcpy(newStr, str, size);
	// 返回字符串指针
	return newStr;
}

void enableThreadSafeness(void) {
	mallocThreadSafe = 1;
}

size_t getUsedMemory(void) {
	size_t um;
#ifdef __unix__
	if (mallocThreadSafe) {
#ifdef HAVE_ATOMIC //是否拥有原子函数库
		// 原子操作：对于多线程对全局变量进行自加1 ，返回没有自增1之前的值，使用此函数，锁住资源
		um = __sync_add_and_fetch(&used_memory, 0);
#else
		// linux 互斥锁 锁定当前的  usedMemoryMutex
		pthread_mutex_lock(&usedMemoryMutex);
		um = usedMemory;
		// linux 互斥锁 解锁当前的  usedMemoryMutex
		pthread_mutex_unlock(&usedMemoryMutex);
#endif // HAVE_ATOMIC
	} else {
		um = usedMemory;
	}
#else
	um = usedMemory;
#endif // __unix__
	return um;
}

float getFragmentationRatio(size_t rss) {
	return (float) rss / (float) getUsedMemory();
}

/**
 * @note
 * 	获取RSS的大小，是指的Resident Set Size，表示当前进程实际所驻留在内存中的空间大小，即不包括被交换（swap）出去的空间
 * 	操作系统中：我们所申请的内存空间不会全部常驻内存，系统会把其中一部分暂时不用的部分从内存中置换到swap区
 * 	linux top命令中，进程状态有两列指标：RSS和SWAP
 */
size_t getMemoryRss(void) {
#ifdef __unix__
	size_t rss;
	int page = sysconf(_SC_PAGESIZE);//调用库函数sysconf()【大家可以man sysconf查看详细内容】来查询内存页的大小
	char buf[4096];
	char filename[256];
	int fd, count;
	char *p, *x;
	//是在当前进程的 /proc/<pid>/stat （<pid>指代当前进程实际id）文件中进行检索
	// 把检索出的绝对路径保存到filename中
	snprintf(filename,256,"/proc/%d/stat",getpid());
	if ((fd = open(filename,O_RDONLY)) == -1) return 0;// 以只读模式打开 /proc/<pid>/stat 文件，然后从中读入4096个字符到字符数组buf中
	if (read(fd,buf,4096) <= 0) {
		close(fd);
		return 0;
	}
	close(fd);
	p = buf;
	// 该文件的第24个字段是RSS的信息，它的单位是pages（内存页的数目）
	count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
	while(p && count--) {
		p = strchr(p,' ');
		if (p) p++; //p++原因是因为，p当前指向的是空格，在执行自增操作之后就指向下一个字段的首地址
	}
	if (!p) return 0; // 判断是否是空指针
	x = strchr(p,' '); // 查找空格在p指针数组中首次出现的地址
	if (!x) return 0;// 如果为NULL就是查询失败
	*x = '\0'; //把最后一个字符设置为 c字符串风格的 ’\0‘

	rss = strtoll(p,NULL,10); //string 转 为10进制的long long类型
	rss *= page; //rss  page相乘并返回，rss获得的实际上是内存页的页数，page保存的是每个内存页的大小（单位字节），相乘之后就表示RSS实际的内存大小了
	return rss;
#else
	return getUsedMemory();
#endif //__unix__
}


size_t getPrivateDirty(void) {
#ifdef __unix__
    char line[1024];
    size_t pd = 0;
    FILE *fp = fopen("/proc/self/smaps","r");
    if (!fp) return 0;
    while(fgets(line,sizeof(line),fp) != NULL) {
        if (strncmp(line,"Private_Dirty:",14) == 0) {
            char *p = strchr(line,'k');
            if (p) {
                *p = '\0';
                pd += strtol(line+14,NULL,10) * 1024;
            }
        }
    }
    fclose(fp);
    return pd;
#else
	return 0; //最坏的结果的内存碎片 0
#endif
}
